#!/usr/bin/env python
#
# Copyright 2010 Sebible Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""An option class to deal with django_ldap model options"""


from django.conf import settings
from django.db.models import signals

from fields import ManyToManyField
from query import get_all_attributes

DEFAULT_NAMES = ('server', 'bind_dn', 'bind_pwd', 'base', 'filter', 'objectClass', 'pk_attr',
                  'rdn', 'no_add', 'single_value_attr', 'group_attr', 'dn_pattern', 'attr_mapping', 'attr', 'no_serialize_fields')

SINGLE_VALUE_ATTR = set(['dn', 'cn', 'sn', 'uid'])

class Options(object):
    def __init__(self, cls):
        """ @todo: ldap settings should be inheratable"""
        # set up default options
        self.server = getattr(settings, 'LDAP_SERVER', None) or 'localhost'
        self.bind_dn = getattr(settings, 'LDAP_BIND_DN', None)
        self.bind_pwd = getattr(settings, 'LDAP_BIND_PWD', None)
        self.base = getattr(settings, 'LDAP_BASE', None)
        self.filter = '(objectClass=*)'
        self.objectClass = 'top'
        self.rdn = None
        self.no_add = False
        self.single_value_attr = SINGLE_VALUE_ATTR
        self.group_attr = {}
        self.dn_pattern = None
        self.attr_mapping = {}
        self.attr = {'dn':[]}
        self.pk_attr = 'id'
        self.no_serialize_fields = []
        
        # override default options with parent class options
        parent_opts = getattr(cls.__base__, '_ldap_options', None)
        if parent_opts:
            self.override(parent_opts)

        # override options with options set within the each model
        cls_opts = getattr(cls, 'Ldap', None)
        if cls_opts:
            self.override(cls_opts)

        # override options with options set in setting files
        settings_opts = getattr(settings, '%s_SETTINGS' % cls._meta.object_name.upper(), None)
        if settings_opts:
            self.override(settings_opts)

        # filter should be wrapped with ()
        if not self.filter.startswith('('):
            self.filter = '(%s)' % self.filter
        
        if not isinstance(self.objectClass, list):
            self.objectClass = [self.objectClass]
        
        if self.rdn and (not isinstance(self.rdn, list)):
            self.rdn = [self.rdn]
        
        for key, value in self.group_attr.items():
            if isinstance(value, dict):
                self.group_attr[key] = ManyToManyField(**value)
        
        self.attr.update(dict([(x, []) for x in get_all_attributes(self.objectClass)]))

    
    def override(self, opts):
        if opts:
            if isinstance(opts, dict):
                opt_attrs = opts.copy()
                opt_dict = opts
            else:
                opt_attrs = opts.__dict__.copy()
                opt_dict = opts.__dict__
            for name in opt_dict:
                # Ignore any private attributes that Django doesn't care about.
                # NOTE: We can't modify a dictionary's contents while looping
                # over it, so we loop over the *original* dictionary instead.
                if name.startswith('_'):
                    del opt_attrs[name]
            for attr_name in DEFAULT_NAMES:
                if attr_name in opt_attrs:
                    if attr_name == 'single_value_attr':
                        attr = opt_attrs.pop(attr_name)
                        if isinstance(attr, list):
                            getattr(self, attr_name).update(set(attr))
                        elif isinstance(attr, set):
                            getattr(self, attr_name).update(attr)
                        else:
                            getattr(self, attr_name).add(attr)
                    elif attr_name == 'attr_mapping' or attr_name == 'group_attr':
                        getattr(self, attr_name).update(opt_attrs.pop(attr_name))
                    else:
                        setattr(self, attr_name, opt_attrs.pop(attr_name))
                elif hasattr(opts, attr_name):
                    setattr(self, attr_name, getattr(opts, attr_name))

            # Any leftover attributes must be invalid.
            if opt_attrs != {}:
                raise TypeError, "'Ldap options' got invalid attribute(s): %s" % ','.join(opt_attrs.keys())

def alias_getter_func(attr):
    def decorate(self):
        return getattr(self, attr)
    return decorate

def alias_setter_func(attr):
    def decorate(self, value):
        return setattr(self, attr, value)
    return decorate

def group_getter_func(field, key):
    def decorate(self):
        return field.get_field(self, key)
    return decorate

def add_ldap_options_to_model(sender, **kwargs):
    from models import Model
    if issubclass(sender, Model):
        setattr(sender, '_ldap_options', Options(sender))
        for key, value in sender._ldap_options.attr_mapping.items():
            setattr(sender, key, property(alias_getter_func(value), alias_setter_func(value)))
        for key, value in sender._ldap_options.group_attr.items():
            setattr(sender, key, property(group_getter_func(value, key)))


signals.class_prepared.connect(add_ldap_options_to_model)